home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 2: Applications / Linux Cubed Series 2 - Applications.iso / editors / emacs / xemacs / xemacs-1.006 / xemacs-1 / lib / xemacs-19.13 / lisp / hyperbole / wrolo.el < prev    next >
Encoding:
Text File  |  1995-05-20  |  44.6 KB  |  1,152 lines

  1. ;;!emacs
  2. ;;
  3. ;; FILE:         wrolo.el V2 (Renamed from rolo.el in earlier versions to avoid
  4. ;;                            load path conflicts with the rolo.el written by
  5. ;;                            another author.)
  6. ;; SUMMARY:      Hierarchical, multi-file, easy to use rolodex system
  7. ;; USAGE:        GNU Emacs Lisp Library
  8. ;; KEYWORDS:     hypermedia, matching
  9. ;;
  10. ;; AUTHOR:       Bob Weiner
  11. ;; ORG:          Motorola Inc.
  12. ;;
  13. ;; ORIG-DATE:     7-Jun-89 at 22:08:29
  14. ;; LAST-MOD:     18-May-95 at 14:19:25 by Bob Weiner
  15. ;;
  16. ;; This file is part of Hyperbole.
  17. ;; Available for use and distribution under the same terms as GNU Emacs.
  18. ;;
  19. ;; Copyright (C) 1989-1995, Free Software Foundation, Inc.
  20. ;; Developed with support from Motorola Inc.
  21. ;;
  22. ;; DESCRIPTION:  
  23. ;;
  24. ;;  The `put whatever you feel like into it' rolodex.
  25. ;;
  26. ;;  FEATURES:
  27. ;;
  28. ;;   1.  Multiple rolodex files with free text lookup.  No structured
  29. ;;       fields are used.
  30. ;;
  31. ;;   2.  Hierarchical rolodex entries as in:
  32. ;;        *    Company
  33. ;;        **     Manager
  34. ;;        ***      Underlings
  35. ;;
  36. ;;       Searching for Manager turns up all Underlings.  Searching for
  37. ;;       Company retrieves all listed employees.
  38. ;;
  39. ;;       This hierarchical system has proved very effective for retrieving
  40. ;;       computer system administration problem reports by vendor name,
  41. ;;       problem number or by subject area, without having to resort to a
  42. ;;       database system, and also for extraction of relevant text
  43. ;;       sections from reports.
  44. ;;
  45. ;;   3.  String and regular expression searching capabilities.  Matches are
  46. ;;       found anywhere within entries, so entries may be of any format you
  47. ;;       like without the bother of fixed field restrictions.
  48. ;;       Ability to restrict number of matches or to report number of matches
  49. ;;       without displaying entries.
  50. ;;
  51. ;;   4.  Smart addition, editing and sorting of entries by hierarchy level.
  52. ;;
  53. ;;   5.  Support for Hyperbole buttons within rolodex entries.
  54. ;;
  55. ;;   See "wrolo-logic.el" for logical search functions (and, or, not, xor).
  56. ;;   See "wrolo-menu.el" for menu handling functions.  (If you received
  57. ;;   wrolo as part of Hyperbole, this file in unneeded and so not included.)
  58. ;;
  59. ;;
  60. ;;  SETUP:
  61. ;;
  62. ;;   The variable 'rolo-file-list' is a list of files to search for
  63. ;;   matching rolodex entries.  To add personal files to rolo-file-list,
  64. ;;   when you find these functions are useful for any sort of list lookup,
  65. ;;   add the following to your ~/.emacs file (substituting where you see
  66. ;;   <fileN>):
  67. ;;
  68. ;;      (setq rolo-file-list (append rolo-file-list '("<file1>" "<file2>")))
  69. ;;
  70. ;;   We recommend that entries in 'rolo-file-list' have ".otl" suffixes
  71. ;;   so that they do not conflict with file names that other rolodex
  72. ;;   programs might use and so that they are edited in 'outline-mode' by
  73. ;;   default.  If you want the latter behavior, uncomment and add something
  74. ;;   like the following to one of your GNU Emacs initialization files:
  75. ;;
  76. ;;     ;; Add to the list of suffixes that causes automatic mode invocation
  77. ;;     (setq auto-mode-alist
  78. ;;        (append '(("\\.otl$" . outline-mode)) auto-mode-alist))
  79. ;;
  80. ;;   The buffers containing the rolodex files are not killed after a search
  81. ;;   on the assumption that another search is likely to follow within this
  82. ;;   Emacs session.  You may wish to change this behavior with the following
  83. ;;   setting:
  84. ;;
  85. ;;     (setq rolo-kill-buffers-after-use t)
  86. ;;
  87. ;;   After an entry is killed, the modified rolodex file is automatically
  88. ;;   saved.  If you would rather always save files yourself, use this
  89. ;;   setting:
  90. ;;
  91. ;;     (setq rolo-save-buffers-after-use nil)
  92. ;;
  93. ;;   When adding an entry from within a buffer containing a mail
  94. ;;   message, the rolodex add function will extract the sender's name
  95. ;;   and e-mail address and prompt you with the name as a default.  If
  96. ;;   you accept it, it will enter the name and the email address using
  97. ;;   the format given by the 'rolo-email-format' variable.  See its
  98. ;;   documentation if you want to change its value.
  99. ;;
  100. ;;
  101. ;;   If you use Hyperbole V2.3 or greater, then no other rolodex setup
  102. ;;   is necessary, simply select the "Rolo/" menu item from the top
  103. ;;   level Hyperbole menu.  Otherwise, add the following to your
  104. ;;   "~/.emacs" file: 
  105. ;;
  106. ;;     (autoload 'rolo-menu "rolo-menu" "Load wrolo system." t)
  107. ;;     (global-set-key "\C-x4r" 'rolo-menu)
  108. ;;
  109. ;;   And then simply invoke the rolodex menu with {C-x 4 r} after Emacs
  110. ;;   has read those lines in your init file.
  111. ;;
  112. ;;
  113. ;;  SUMMARY OF USE:
  114. ;;
  115. ;;   The rolo menu provides access to the following commands:
  116. ;;
  117. ;;     Menu Item       Function              Description
  118. ;;     ====================================================================
  119. ;;     Add             rolo-add              Adds a rolodex entry
  120. ;;     Display         rolo-display-matches  Displays last matches again
  121. ;;     Edit            rolo-edit             Edits an existing rolodex entry
  122. ;;     Info                                  Displays Rolodex manual entry
  123. ;;     Kill            rolo-kill             Removes an entry from the rolodex
  124. ;;     Order           rolo-sort             Sorts all levels in rolodex
  125. ;;     RegexFind       rolo-grep             Finds all entries containing
  126. ;;                                             a regular expression
  127. ;;     StringFind      rolo-fgrep            Finds all entries containing
  128. ;;                                             a string
  129. ;;     WordFind        rolo-word             Finds all entries containing
  130. ;;                                             a string of whole words
  131. ;;     Yank            rolo-yank             Inserts first matching rolodex
  132. ;;                                             entry at point
  133. ;;
  134. ;;   For any of these commands that prompt you for a name, you may use the form
  135. ;;   parent/child to locate a child entry below a parent entry, e.g.
  136. ;;   from the example near the top, we could give Company/Manager/Underlings.
  137. ;;
  138. ;;   Here is a snippet from our group rolodex file.  The ';'s should be
  139. ;;   removed of course and the '*'s should begin at the start of the
  140. ;;   line.  If a rolodex file begins with two separator lines whose
  141. ;;   first three characters are "===", then these lines and any text
  142. ;;   between them are prepended to the output buffer whenever any
  143. ;;   entries are retrieved from that file.
  144. ;;
  145. ;;=============================================================================
  146. ;;                  GROUP ROLODEX
  147. ;; <Last Name>, <First Name>  <Co/Categ>   W<Work #>   H<Home #>  P<Pager #>
  148. ;;                           F<Fax #>    M<Modem #> C<Cellular #>
  149. ;;        <Address>       <Miscellaneous Info, Key Words>
  150. ;;=============================================================================
  151. ;;*   EX594, Digital-Systems-Research
  152. ;;**  Weiner, Bob          Motorola     W2087                  P7-7489
  153. ;;      FL19, L-1035
  154. ;;
  155. ;;
  156. ;;  FOR PROGRAMMERS:
  157. ;;
  158. ;;   Entries in rolodex files are separated by patterns matching
  159. ;;   'rolo-entry-regexp'.  Each entry may have any number of sub-entries
  160. ;;   which represent the next level down in the entry hierarchy.
  161. ;;   Sub-entries' separator patterns are always longer than their parents'.
  162. ;;   For example, if an entry began with '*' then its sub-entries would begin
  163. ;;   with '**' and so on.  Blank lines in rolodex files will not end up where
  164. ;;   you want them if you use the rolo-sort commands; therefore, blank lines
  165. ;;   are not recommended.  If you change the value of
  166. ;;   'rolo-entry-regexp', you will have to modify 'rolo-sort'.
  167. ;;
  168. ;;   The following additional functions are provided:
  169. ;;
  170. ;;     'rolo-sort-level' sorts a specific level of entries in a rolodex file;
  171. ;;     'rolo-map-level' runs a user specified function on a specific level of
  172. ;;       entries in a rolodex file;
  173. ;;     'rolo-fgrep-file', same as 'rolo-fgrep' but operates on a single file;
  174. ;;     'rolo-grep-file', same as 'rolo-grep' but operates on a single file;
  175. ;;     'rolo-display-matches', display last set of rolodex matches, if any;
  176. ;;     'rolo-toggle-narrow-to-entry' toggles between display of current entry
  177. ;;       and display of all matching entries.
  178. ;;
  179. ;;
  180. ;;  MOD HISTORY:
  181. ;;
  182. ;;   12/17/89
  183. ;;     Added internal 'rolo-shrink-window' function for use in
  184. ;;     compressing/uncompressing the rolo view window to/from a size just
  185. ;;     large enough for the selected entry.  This is useful when a search
  186. ;;     turns up more entries than desired.
  187. ;;
  188. ;;   02/21/90
  189. ;;     Modified 'rolo-grep-file' and 'rolo-map-level' so they only set buffers
  190. ;;     read-only the first time they are read in.  This way, if someone edits a
  191. ;;     rolodex file and then does a rolo-fgrep or other function, the buffer
  192. ;;     will not be back in read-only mode.
  193. ;;
  194. ;;   04/18/91
  195. ;;     Modified 'rolo-grep-file' to expand any hidden entries in rolo file
  196. ;;     before doing a search.
  197. ;;
  198. ;;   12/24/91
  199. ;;     Added Hyperbole button support.
  200. ;;
  201. ;;   12/30/91
  202. ;;     Added convenient support for entry add, edit, kill and yank.
  203. ;;
  204. ;;   01/10/91
  205. ;;     Fixed bug in rolo-to that ended search too early.
  206. ;;
  207. ;; DESCRIP-END.
  208.  
  209. ;;; ************************************************************************
  210. ;;; Other required Elisp libraries
  211. ;;; ************************************************************************
  212.  
  213. (require 'hversion)
  214.  
  215. ;;; ************************************************************************
  216. ;;; Public variables
  217. ;;; ************************************************************************
  218.  
  219. (defvar rolo-email-format "%s\t\t<%s>"
  220.   "Format string to use when adding an entry with e-mail addr from a mail msg.
  221. It must contain a %s indicating where to put the entry name and a second
  222. %s indicating where to put the e-mail address.")
  223.  
  224. (defvar rolo-file-list '("~/.rolodex.otl")
  225.   "*List of files containing rolodex entries.
  226. The first file should be a user-specific rolodex file, typically in the home
  227. directory.  The second file is often a shared, group-specific rolodex file.
  228.  
  229. A rolo-file consists of:
  230.    (1) an optional header beginning with and ending with a line which matches
  231.        rolo-hdr-regexp;
  232.    (2) one or more rolodex entries which each begin with
  233.        rolo-entry-regexp and may be nested.")
  234.  
  235. (defvar rolo-highlight-face nil
  236.   "*Face used to highlight rolodex search matches.")
  237. (if rolo-highlight-face
  238.     nil
  239.   (setq rolo-highlight-face
  240.     (cond (hyperb:emacs19-p
  241.            (if (fboundp 'make-face)
  242.            (progn (make-face 'rolo-highlight-face)
  243.               'rolo-highlight-face)))
  244.           (hyperb:epoch-p (make-style))
  245.           (t (if (fboundp 'make-face)
  246.              (face-name (make-face 'rolo-highlight-face))))))
  247.   (if (fboundp 'hproperty:set-item-highlight)
  248.       (hproperty:set-item-highlight)))
  249.  
  250. (defvar rolo-kill-buffers-after-use nil
  251.   "*Non-nil means kill rolodex file buffers after searching them for entries.
  252. Only unmodified buffers are killed.")
  253.  
  254. (defvar rolo-save-buffers-after-use t
  255.   "*Non-nil means save rolodex file after an entry is killed.")
  256.  
  257. ;;; ************************************************************************
  258. ;;; Commands
  259. ;;; ************************************************************************
  260.  
  261. ;;;###autoload
  262. (defun rolo-add (name &optional file)
  263.   "Adds a new entry in personal rolodex for NAME.
  264. Last name first is best, e.g. \"Smith, John\".
  265. With prefix argument, prompts for optional FILE to add entry within.
  266. NAME may be of the form: parent/child to insert child below a parent
  267. entry which begins with the parent string."
  268.   (interactive
  269.    (progn
  270.      (or (fboundp 'mail-fetch-field) (require 'mail-utils))
  271.      (let* ((lst (rolo-name-and-email))
  272.         (name (car lst))
  273.         (email (car (cdr lst)))
  274.         (entry (read-string "Name to add to rolo: "
  275.                 (or name email))))
  276.        (list (if (and email name
  277.               (string-match (concat "\\`" (regexp-quote entry)) name))
  278.          (format rolo-email-format entry email) entry)
  279.          current-prefix-arg))))
  280.   (if (or (not (stringp name)) (string= name ""))
  281.       (error "(rolo-add): Invalid name: '%s'" name))
  282.   (if (and (interactive-p) file)
  283.       (setq file (completing-read "File to add to: "
  284.                   (mapcar 'list rolo-file-list))))
  285.   (if (null file) (setq file (car rolo-file-list)))
  286.   (cond ((and file (or (not (stringp file)) (string= file "")))
  287.      (error "(rolo-add): Invalid file: '%s'" file))
  288.     ((and (file-exists-p file) (not (file-readable-p file)))
  289.      (error "(rolo-add): File not readable: '%s'" file))
  290.     ((not (file-writable-p file))
  291.      (error "(rolo-add): File not writable: '%s'" file)))
  292.   (set-buffer (or (get-file-buffer file) (find-file-noselect file)))
  293.   (if (interactive-p) (message "Locating insertion point for '%s'..." name))
  294.   (let ((parent "") (level "") end)
  295.     (widen) (goto-char 1)
  296.     (while (setq end (string-match "/" name))
  297.       (setq parent (substring name 0 end)
  298.         name (substring name (min (1+ end) (length name))))
  299.       (if (re-search-forward
  300.        (concat "\\(" rolo-entry-regexp "\\)[ \t]*" 
  301.            (regexp-quote parent)) nil t)
  302.       (setq level (buffer-substring (match-beginning 1)
  303.                     (match-end 1)))
  304.     (error "(rolo-add): '%s' category not found in \"%s\"."
  305.            parent file)))
  306.     (narrow-to-region (point)
  307.               (progn (rolo-to-entry-end t level) (point)))
  308.     (goto-char (point-min))
  309.     (let* ((len (length name))
  310.        (name-level (concat level "*"))
  311.        (level-len (length name-level))
  312.        (entry "")
  313.        (entry-spc "")
  314.        (entry-level)
  315.        (match)
  316.        (again t))
  317.       (while (and again
  318.           (re-search-forward
  319.            (concat "\\(" rolo-entry-regexp "\\)\\([ \t]*\\)")
  320.            nil 'end))
  321.     (setq entry-level (buffer-substring (match-beginning 1)
  322.                         (match-end 1)))
  323.     (if (/= (length entry-level) level-len)
  324.         (rolo-to-entry-end t entry-level)
  325.       (setq entry (buffer-substring (point) (+ (point) len))
  326.         entry-spc (buffer-substring (match-beginning 2)
  327.                         (match-end 2)))
  328.       (cond ((string< entry name)
  329.          (rolo-to-entry-end t entry-level))
  330.         ((string< name entry)
  331.          (setq again nil) (beginning-of-line))
  332.         (t ;; found existing entry matching name
  333.          (setq again nil match t)))))
  334.       (setq buffer-read-only nil)
  335.       (if match
  336.       nil
  337.     (insert (or entry-level (concat level "*"))
  338.         (if (string= entry-spc "") "   " entry-spc)
  339.         name "\n")
  340.     (backward-char 1))
  341.       (widen)
  342.       (rolo-to-buffer (current-buffer))
  343.       ;; Fixes non-display update bug when buf is on screen before
  344.       ;; interactive command invocation. 
  345.       (goto-char (point))
  346.       (if (interactive-p)
  347.       (message "Edit entry at point.")))))
  348.  
  349. ;;;###autoload
  350. (defun rolo-display-matches (&optional display-buf return-to-buffer)
  351.   "Display optional DISPLAY-BUF buffer of previously found rolodex matches.
  352. If DISPLAY-BUF is nil, use the value in 'rolo-display-buffer'.
  353. Second arg RETURN-TO-BUFFER is the buffer to leave point within after the display."
  354.   (interactive)
  355.   (or display-buf (setq display-buf (get-buffer rolo-display-buffer)))
  356.   (if display-buf nil
  357.     (error "(rolo-display-matches): Search the rolodex first."))
  358.   ;; Save current window configuration if rolodex match buffer is not
  359.   ;; displayed in one of the windows already.
  360.   (or
  361.    ;; Handle both Emacs V18 and V19 versions of get-buffer-window.
  362.    (condition-case ()
  363.        (get-buffer-window display-buf (selected-frame))
  364.      (error (get-buffer-window display-buf)))
  365.    (setq *rolo-wconfig* (current-window-configuration)))
  366.   (rolo-to-buffer display-buf t)
  367.   (if (eq major-mode 'wrolo-mode) nil (wrolo-mode))
  368.   (setq buffer-read-only nil)
  369.   (if (fboundp 'hproperty:but-create) (hproperty:but-create))
  370.   (rolo-shrink-window)
  371.   (goto-char (point-min))
  372.   (set-buffer-modified-p nil)
  373.   (setq buffer-read-only t)
  374.   (run-hooks 'wrolo-display-hook)
  375.   ;; Leave point in match buffer unless a specific RETURN-TO-BUFFER has
  376.   ;; been specified.  Use {q} to quit and restore display.
  377.   (if return-to-buffer (rolo-to-buffer return-to-buffer t)))
  378.  
  379. ;;;###autoload
  380. (defun rolo-edit (&optional name file)
  381.   "Edits a rolodex entry given by optional NAME within 'rolo-file-list'.
  382. With prefix argument, prompts for optional FILE to locate entry within.
  383. With no NAME arg, simply displays FILE or first entry in 'rolo-file-list' in an
  384. editable mode.  NAME may be of the form: parent/child to edit child below a
  385. parent entry which begins with the parent string."
  386.   (interactive "sName to edit in rolo: \nP")
  387.   (if (or (and name (not (stringp name))) (string= name ""))
  388.       (error "(rolo-edit): Invalid name: '%s'" name))
  389.   (if (and (interactive-p) current-prefix-arg)
  390.       (setq file (completing-read "Entry's File: "
  391.                   (mapcar 'list rolo-file-list))))
  392.   (let ((found-point) (file-list (if file (list file) rolo-file-list)))
  393.     (or file (setq file (car file-list)))
  394.     (if (null name)
  395.     (progn (if (not (file-writable-p file))
  396.           (error "(rolo-edit): File not writable: '%s'" file))
  397.            (find-file-other-window file) (setq buffer-read-only nil))
  398.       (if (setq found-point (rolo-to name file-list))
  399.       (progn
  400.         (setq file buffer-file-name)
  401.         (if (file-writable-p file)
  402.         (setq buffer-read-only nil)
  403.           (message
  404.            "(rolo-edit): Entry found but file not writable: '%s'" file)
  405.           (beep))
  406.         (rolo-to-buffer (current-buffer)))
  407.     (message "(rolo-edit): '%s' not found." name)
  408.     (beep)
  409.     (rolo-to-buffer (or (get-file-buffer (car file-list))
  410.                 (find-file-noselect (car file-list))))
  411.     (setq buffer-read-only nil))
  412.       (widen)
  413.       ;; Fixes display update bug in some Emacs versions.  When buf is
  414.       ;; on screen before interactive command invocation, point is not
  415.       ;; moved to proper location.
  416.       (if found-point (goto-char found-point)))))
  417.  
  418. (defun rolo-edit-entry ()
  419.   "Edit the source entry of the rolodex match buffer entry at point.
  420. Returns entry name if found, else nil."
  421.   (interactive)
  422.   (let ((name (rolo-name-at)))
  423.     (if name (progn (rolo-edit name (hbut:key-src))
  424.             name))))
  425.  
  426. ;;;###autoload
  427. (defun rolo-fgrep (string
  428.             &optional max-matches rolo-file count-only no-display)
  429.   "Display rolodex entries matching STRING.
  430. To a maximum of optional prefix arg MAX-MATCHES, in file(s) from optional
  431. ROLO-FILE or rolo-file-list.  Default is to find all matching entries.  Each
  432. entry is displayed with all of its sub-entries.  Optional COUNT-ONLY non-nil
  433. means don't retrieve and don't display matching entries.  Optional NO-DISPLAY
  434. non-nil means retrieve entries but don't display.
  435.  
  436. Nil value of MAX-MATCHES means find all matches, t value means find all matches
  437. but omit file headers, negative values mean find up to the inverse of that
  438. number of entries and omit file headers.
  439.  
  440. Returns number of entries matched.  See also documentation for the variable
  441. rolo-file-list."
  442.   (interactive "sRolodex string to match: \nP")
  443.   (let ((total-matches (rolo-grep (regexp-quote string) max-matches
  444.                   rolo-file count-only no-display)))
  445.     (if (interactive-p)
  446.     (message "%s matching entr%s found in rolodex."
  447.          (if (= total-matches 0) "No" total-matches)
  448.          (if (= total-matches 1) "y" "ies")))
  449.     total-matches))
  450.  
  451. ;;;###autoload
  452. (defun rolo-grep (regexp &optional max-matches rolo-bufs count-only no-display)
  453.   "Display rolodex entries matching REGEXP.
  454. To a maximum of prefix arg MAX-MATCHES, in buffer(s) from optional ROLO-BUFS or
  455. rolo-file-list.  Default is to find all matching entries.  Each entry is
  456. displayed with all of its sub-entries.  Optional COUNT-ONLY non-nil means don't
  457. retrieve and don't display matching entries.  Optional NO-DISPLAY non-nil
  458. means retrieve entries but don't display.
  459.  
  460. Nil value of MAX-MATCHES means find all matches, t value means find all matches
  461. but omit file headers, negative values mean find up to the inverse of that
  462. number of entries and omit file headers.
  463.  
  464. Returns number of entries matched.  See also documentation for the variable
  465. rolo-file-list."
  466.   (interactive "sRolodex regular expression to match: \nP")
  467.   (let ((rolo-file-list
  468.      (cond ((null rolo-bufs) rolo-file-list)
  469.            ((listp rolo-bufs) rolo-bufs)
  470.            ((list rolo-bufs))))
  471.     (display-buf (if count-only
  472.              nil
  473.                (set-buffer (get-buffer-create rolo-display-buffer))))
  474.     (total-matches 0)
  475.     (num-matched 0)
  476.     (inserting (or (eq max-matches t)
  477.                (and (integerp max-matches) (< max-matches 0))))
  478.     (file))
  479.     (if count-only nil
  480.       (setq buffer-read-only nil) 
  481.       (or inserting (erase-buffer)))
  482.     (while (and (setq file (car rolo-file-list))
  483.         (or (not (integerp max-matches))
  484.             (< total-matches (max max-matches (- max-matches)))))
  485.       (setq rolo-file-list (cdr rolo-file-list)
  486.         num-matched (rolo-grep-file file regexp max-matches count-only)
  487.         total-matches (+ total-matches num-matched))
  488.       (if (integerp max-matches)
  489.       (setq max-matches
  490.         (if (>= max-matches 0)
  491.             (- max-matches num-matched)
  492.           (+ max-matches num-matched)))))
  493.     (if (or count-only no-display inserting (= total-matches 0))
  494.     nil
  495.       (rolo-display-matches display-buf))
  496.     (if (interactive-p)
  497.     (message "%s matching entr%s found in rolodex."
  498.          (if (= total-matches 0) "No" total-matches)
  499.          (if (= total-matches 1) "y" "ies")
  500.          ))
  501.     total-matches))
  502.  
  503. ;;;###autoload
  504. (defun rolo-kill (name &optional file)
  505.   "Kills a rolodex entry given by NAME within 'rolo-file-list'.
  506. With prefix argument, prompts for optional FILE to locate entry within.
  507. NAME may be of the form: parent/child to kill child below a parent entry
  508. which begins with the parent string.
  509. Returns t if entry is killed, nil otherwise."
  510.   (interactive "sName to kill in rolo: \nP")
  511.   (if (or (not (stringp name)) (string= name ""))
  512.       (error "(rolo-kill): Invalid name: '%s'" name))
  513.   (if (and (interactive-p) current-prefix-arg)
  514.       (setq file (completing-read "Entry's File: "
  515.                   (mapcar 'list rolo-file-list))))
  516.   (let ((file-list (if file (list file) rolo-file-list))
  517.     (killed))
  518.     (or file (setq file (car file-list)))
  519.     (if (rolo-to name file-list)
  520.     (progn
  521.       (setq file buffer-file-name)
  522.       (if (file-writable-p file)
  523.           (let ((kill-op
  524.              (function (lambda (start level)
  525.                  (kill-region
  526.                   start (rolo-to-entry-end t level))
  527.                  (setq killed t)
  528.                  (rolo-save-buffer)
  529.                  (rolo-kill-buffer))))
  530.             start end level)
  531.         (setq buffer-read-only nil)
  532.         (re-search-backward rolo-entry-regexp nil t)
  533.         (setq end (match-end 0))
  534.         (beginning-of-line)
  535.         (setq start (point)
  536.               level (buffer-substring start end))
  537.         (goto-char end)
  538.         (skip-chars-forward " \t")
  539.         (if (interactive-p)
  540.             (let ((entry-line (buffer-substring
  541.                        (point)
  542.                        (min (+ (point) 60)
  543.                         (progn (end-of-line) (point))))))
  544.               (if (y-or-n-p (format "Kill `%s...' " entry-line))
  545.               (progn
  546.                 (funcall kill-op start level)
  547.                 (message "Killed"))
  548.             (message "Aborted")))
  549.           (funcall kill-op start level)))
  550.         (message
  551.          "(rolo-kill): Entry found but file not writable: '%s'" file)
  552.         (beep)))
  553.       (message "(rolo-kill): '%s' not found." name)
  554.       (beep))
  555.     killed))
  556.  
  557. (defun rolo-next-match ()
  558.   "Move point forward to the start of the next rolodex search match."
  559.   (interactive)
  560.   (if (not (stringp rolo-match-regexp))
  561.       (error "(rolo-next-match): Invoke a rolodex search expression first"))
  562.   (let ((start (point))
  563.     (case-fold-search t))
  564.     (if (looking-at rolo-match-regexp)
  565.     (goto-char (match-end 0)))
  566.     (if (re-search-forward rolo-match-regexp nil t)
  567.     (goto-char (match-beginning 0))
  568.       (goto-char start)
  569.       (error
  570.        "(rolo-next-match): No following matches for \"%s\"" rolo-match-regexp))))
  571.  
  572. (defun rolo-previous-match ()
  573.   "Move point back to the start of the previous rolodex search match."
  574.   (interactive)
  575.   (if (not (stringp rolo-match-regexp))
  576.       (error "(rolo-previous-match): Invoke a rolodex search expression first"))
  577.   (let ((case-fold-search t))
  578.     (if (re-search-backward rolo-match-regexp nil t)
  579.     nil
  580.       (error
  581.        "(rolo-previous-match): No prior matches for \"%s\"" rolo-match-regexp))))
  582.  
  583. (defun rolo-quit ()
  584.   "Quit from the rolodex match buffer and restore the prior frame display."
  585.   (interactive)
  586.   (bury-buffer)
  587.   (if (and *rolo-wconfig*
  588.        (if (fboundp 'window-configuration-p)
  589.            (window-configuration-p *rolo-wconfig*)
  590.          t))
  591.       (set-window-configuration *rolo-wconfig*)))
  592.  
  593. ;;;###autoload
  594. (defun rolo-sort (&optional rolo-file)
  595.   "Sorts up to 14 levels of entries in ROLO-FILE (default is personal rolo).
  596. Assumes entries are delimited by one or more '*'characters.
  597. Returns list of number of groupings at each entry level." 
  598.   (interactive
  599.    (list (let ((default "")
  600.            (file))
  601.      (setq file
  602.            (completing-read
  603.         (format "Sort rolo file (default %s): "
  604.             (file-name-nondirectory
  605.              (setq default
  606.                    (if (and buffer-file-name
  607.                     (memq
  608.                      t (mapcar
  609.                         (function
  610.                          (lambda (file)
  611.                            (equal buffer-file-name
  612.                               (expand-file-name file))))
  613.                         rolo-file-list)))
  614.                    buffer-file-name
  615.                  (car rolo-file-list)))))
  616.         (mapcar 'list rolo-file-list)))
  617.      (if (string= file "") default file))))
  618.   (if (or (not rolo-file) (equal rolo-file ""))
  619.       (setq rolo-file (car rolo-file-list)))
  620.   (if (not (and (stringp rolo-file) (file-readable-p rolo-file)))
  621.       (error "(rolo-sort): Invalid or unreadable file: %s" rolo-file))
  622.   (let ((level-regexp (regexp-quote "**************"))
  623.     (entries-per-level-list)
  624.     (n))
  625.     (while (not (equal level-regexp ""))
  626.       (setq n (rolo-sort-level rolo-file level-regexp))
  627.       (if (or (/= n 0) entries-per-level-list)
  628.       (setq entries-per-level-list
  629.         (append (list n) entries-per-level-list)))
  630.       (setq level-regexp (substring level-regexp 0 (- (length level-regexp) 2))))
  631.     entries-per-level-list))
  632.  
  633. (defun rolo-sort-level (rolo-file level-regexp &optional max-groupings)
  634.   "Sorts groupings of entries in ROLO-FILE at hierarchy level LEVEL-REGEXP.
  635. To a maximum of optional MAX-GROUPINGS.  Nil value of MAX-GROUPINGS means all
  636. groupings at the given level.  LEVEL-REGEXP should simply match the text of
  637. any rolodex entry of the given level, not the beginning of a line (^); an
  638. example, might be (regexp-quote \"**\") to match level two.  Returns number
  639. of groupings sorted."
  640.   (interactive "sRolodex file to sort: \nRegexp for level's entries: \nP")
  641.   (rolo-map-level
  642.    (function (lambda (start end) (sort-lines nil start end)))
  643.    rolo-file
  644.    level-regexp
  645.    max-groupings))
  646.  
  647. (defun rolo-toggle-narrow-to-entry ()
  648.   "Toggle between display of current entry and display of all matched entries.
  649. Useful when bound to a mouse key."
  650.   (interactive)
  651.   (if (rolo-narrowed-p)
  652.       (widen)
  653.     (if (or (looking-at rolo-entry-regexp)
  654.         (re-search-backward rolo-entry-regexp nil t))
  655.     (progn (forward-char)
  656.            (narrow-to-region (1- (point)) (rolo-display-to-entry-end)))))
  657.   (rolo-shrink-window)
  658.   (goto-char (point-min)))
  659.  
  660. (defun rolo-word (string
  661.           &optional max-matches rolo-file count-only no-display)
  662.   "Display rolodex entries with whole word matches for STRING.
  663. To a maximum of optional prefix arg MAX-MATCHES, in file(s) from optional
  664. ROLO-FILE or rolo-file-list.  Default is to find all matching entries.  Each
  665. entry is displayed with all of its sub-entries.  Optional COUNT-ONLY non-nil
  666. means don't retrieve and don't display matching entries.  Optional NO-DISPLAY
  667. non-nil means retrieve entries but don't display.
  668.  
  669. Nil value of MAX-MATCHES means find all matches, t value means find all matches
  670. but omit file headers, negative values mean find up to the inverse of that
  671. number of entries and omit file headers.
  672.  
  673. Returns number of entries matched.  See also documentation for the variable
  674. rolo-file-list."
  675.   (interactive "sRolodex whole words to match: \nP")
  676.   (let ((total-matches (rolo-grep (format "\\b%s\\b" (regexp-quote string))
  677.                   max-matches
  678.                   rolo-file count-only no-display)))
  679.     (if (interactive-p)
  680.     (message "%s matching entr%s found in rolodex."
  681.          (if (= total-matches 0) "No" total-matches)
  682.          (if (= total-matches 1) "y" "ies")))
  683.     total-matches))
  684.  
  685. ;;;###autoload
  686. (defun rolo-yank (name &optional regexp-p)
  687.   "Inserts at point the first rolodex entry matching NAME.
  688. With optional prefix arg, REGEXP-P, treats NAME as a regular expression instead
  689. of a string."
  690.   (interactive "sName to insert record for: \nP")
  691.   (let ((rolo-display-buffer (current-buffer)))
  692.     (if regexp-p
  693.     (rolo-grep name -1)
  694.       (rolo-grep (regexp-quote name) -1))))
  695.  
  696. ;;; ************************************************************************
  697. ;;; Public functions
  698. ;;; ************************************************************************
  699.  
  700. (defun rolo-fgrep-file (rolo-buf string &optional max-matches count-only)
  701.   "Retrieve entries in ROLO-BUF matching STRING to a maximum of optional MAX-MATCHES.
  702. Nil value of MAX-MATCHES means find all matches, t value means find all matches
  703. but omit file headers, negative values mean find up to the inverse of that
  704. number of entries and omit file headers.  Optional COUNT-ONLY non-nil
  705. means don't retrieve matching entries.
  706. Returns number of matching entries found."
  707.   (rolo-grep-file rolo-buf (regexp-quote string) max-matches count-only))
  708.  
  709. (defun rolo-grep-file (rolo-buf regexp &optional max-matches count-only)
  710.   "Retrieve entries in ROLO-BUF matching REGEXP to a maximum of optional MAX-MATCHES.
  711. Nil value of MAX-MATCHES means find all matches, t value means find all matches
  712. but omit file headers, negative values mean find up to the inverse of that
  713. number of entries and omit file headers.  Optional COUNT-ONLY non-nil
  714. means don't retrieve matching entries.
  715. Returns number of matching entries found."
  716.   ;;
  717.   ;; Save regexp as last rolodex search expression.
  718.   (setq rolo-match-regexp regexp)
  719.   ;;
  720.   (let ((new-buf-p) (actual-buf))
  721.     (if (and (or (null max-matches) (eq max-matches t) (integerp max-matches))
  722.          (or (setq actual-buf (rolo-buffer-exists-p rolo-buf))
  723.          (if (file-exists-p rolo-buf)
  724.              (setq actual-buf (find-file-noselect rolo-buf t)
  725.                new-buf-p t))))
  726.     (let ((hdr-pos) (num-found 0) (curr-entry-level)
  727.           (incl-hdr t))
  728.       (if max-matches
  729.           (cond ((eq max-matches t)
  730.              (setq incl-hdr nil max-matches nil))
  731.             ((< max-matches 0)
  732.              (setq incl-hdr nil
  733.                max-matches (- max-matches)))))
  734.       (set-buffer actual-buf)
  735.       (if new-buf-p (setq buffer-read-only t))
  736.       (save-excursion
  737.         (save-restriction
  738.           (widen)
  739.           (goto-char 1)
  740.           ;; Ensure no entries in outline mode are hidden.
  741.           ;; Uses 'show-all' function from outline.el.
  742.           (and (search-forward "\C-m" nil t)
  743.            (show-all))
  744.           (if (re-search-forward rolo-hdr-regexp nil t 2)
  745.           (progn (forward-line)
  746.              (setq hdr-pos (cons (point-min) (point)))))
  747.           (re-search-forward rolo-entry-regexp nil t)
  748.           (while (and (or (null max-matches) (< num-found max-matches))
  749.               (re-search-forward regexp nil t))
  750.         (re-search-backward rolo-entry-regexp nil t)
  751.         (let ((start (point))
  752.               (next-entry-exists))
  753.           (re-search-forward rolo-entry-regexp nil t)
  754.           (setq curr-entry-level (buffer-substring start (point)))
  755.           (rolo-to-entry-end t curr-entry-level)
  756.           (or count-only
  757.               (if (and (= num-found 0) incl-hdr)
  758.               (let* ((src (or (buffer-file-name actual-buf)
  759.                       actual-buf))
  760.                  (src-line
  761.                    (format
  762.                      (concat (if (boundp 'hbut:source-prefix)
  763.                          hbut:source-prefix
  764.                            "@loc> ")
  765.                          "%s")
  766.                      (prin1-to-string src))))
  767.                 (set-buffer rolo-display-buffer)
  768.                 (goto-char (point-max))
  769.                 (if hdr-pos
  770.                 (progn
  771.                   (insert-buffer-substring
  772.                     actual-buf (car hdr-pos) (cdr hdr-pos))
  773.                   (insert src-line "\n\n"))
  774.                   (insert (format rolo-hdr-format src-line)))
  775.                 (set-buffer actual-buf))))
  776.           (setq num-found (1+ num-found))
  777.           (or count-only
  778.               (rolo-add-match rolo-display-buffer regexp start (point)))))))
  779.       (rolo-kill-buffer actual-buf)
  780.       num-found)
  781.       0)))
  782.  
  783. (defun rolo-map-level (func rolo-buf level-regexp &optional max-groupings)
  784.   "Perform FUNC on groupings of ROLO-BUF entries at level LEVEL-REGEXP,
  785. to a maximum of optional argument MAX-GROUPINGS.  Nil value of MAX-GROUPINGS
  786. means all groupings at the given level.  FUNC should take two arguments, the
  787. start and the end of the region that it should manipulate.  LEVEL-REGEXP
  788. should simply match the text of any rolodex entry of the given level, not the
  789. beginning of a line (^); an example, might be (regexp-quote \"**\") to match
  790. level two.  Returns number of groupings matched."
  791.   (let ((actual-buf))
  792.     (if (and (or (null max-groupings) (< 0 max-groupings))
  793.          (or (setq actual-buf (rolo-buffer-exists-p rolo-buf))
  794.          (if (file-exists-p rolo-buf)
  795.              (progn (setq actual-buf (find-file-noselect rolo-buf t))
  796.                 t))))
  797.     (progn
  798.       (set-buffer actual-buf)
  799.       (let ((num-found 0)
  800.         (exact-level-regexp (concat "^\\(" level-regexp "\\)[ \t\n]"))
  801.         (outline-regexp rolo-entry-regexp)
  802.         (buffer-read-only)
  803.         (level-len))
  804.         ;; Load 'outline' library since its functions are used here.
  805.         (if (not (boundp 'outline-mode-map))
  806.         (load-library "outline"))
  807.         (goto-char (point-min))
  808.         ;; Pass buffer header if it exists
  809.         (if (re-search-forward rolo-hdr-regexp nil t 2)
  810.         (forward-line))
  811.         (while (and (or (null max-groupings) (< num-found max-groupings))
  812.             (re-search-forward exact-level-regexp nil t))
  813.           (setq num-found (1+ num-found))
  814.           (let* ((opoint (prog1 (point) (beginning-of-line)))
  815.              (grouping-start (point))
  816.              (start grouping-start)
  817.              (level-len (or level-len (- (1- opoint) start)))
  818.              (next-level-len)
  819.              (next-entry-exists)
  820.              (grouping-end)
  821.              (no-subtree))
  822.         (while (and (progn
  823.                   (if (setq next-entry-exists
  824.                     (re-search-forward
  825.                      rolo-entry-regexp nil t 2))
  826.                   (setq next-level-len
  827.                     (- (point)
  828.                        (progn (beginning-of-line)
  829.                           (point)))
  830.                     grouping-end
  831.                     (< next-level-len level-len)
  832.                     no-subtree
  833.                     (<= next-level-len level-len))
  834.                 (setq grouping-end t no-subtree t)
  835.                 (goto-char (point-max)))
  836.                   (let ((end (point)))
  837.                 (goto-char start)
  838.                 (hide-subtree) ; And hide multiple entry lines
  839.                 ;; Move to start of next entry at equal
  840.                 ;; or higher level.
  841.                 (setq start
  842.                       (if no-subtree
  843.                       end
  844.                     (if (re-search-forward
  845.                          rolo-entry-regexp nil t)
  846.                         (progn (beginning-of-line) (point))
  847.                       (point-max))))
  848.                 ;; Remember last expression in 'progn'
  849.                 ;; must always return non-nil.
  850.                 (goto-char start)))
  851.                 (not grouping-end)))
  852.         (let ((end (point)))
  853.           (goto-char grouping-start)
  854.           (funcall func grouping-start end)
  855.           (goto-char end))))
  856.         (show-all)
  857.         (rolo-kill-buffer actual-buf)
  858.         num-found))
  859.       0)))
  860.  
  861. ;;; ************************************************************************
  862. ;;; Private functions
  863. ;;; ************************************************************************
  864.  
  865. (defun rolo-add-match (rolo-matches-buffer regexp start end)
  866.   "Insert before point in ROLO-MATCHES-BUFFER an entry matching REGEXP from the current region between START to END."
  867.   (let ((rolo-buf (current-buffer))
  868.     opoint)
  869.     (set-buffer (get-buffer-create rolo-matches-buffer))
  870.     (setq opoint (point))
  871.     (insert-buffer-substring rolo-buf start end)
  872.     (rolo-highlight-matches regexp opoint (point))
  873.     (set-buffer rolo-buf)))
  874.  
  875. (defun rolo-buffer-exists-p (rolo-buf)
  876.   "Returns buffer given by ROLO-BUF or nil.
  877. ROLO-BUF may be a file-name, buffer-name, or buffer."
  878.   (car (memq (get-buffer (or (and (stringp rolo-buf)
  879.                   (get-file-buffer rolo-buf))
  880.                  rolo-buf))
  881.          (buffer-list))))
  882.  
  883. (defun rolo-display-to-entry-end ()
  884.   "Go to end of current entry, ignoring sub-entries."
  885.   (if (re-search-forward (concat rolo-hdr-regexp "\\|"
  886.                  rolo-entry-regexp) nil t)
  887.       (progn (beginning-of-line) (point))
  888.     (goto-char (point-max))))
  889.  
  890.       
  891. (defun rolo-format-name (name-str first last)
  892.   "Reverse order of NAME-STR field given my regexp match field FIRST and LAST."
  893.   (if (match-beginning last)
  894.       (concat (substring name-str (match-beginning last) (match-end last))
  895.           ", "
  896.           (substring name-str (match-beginning first) (match-end first)))))
  897.  
  898. (defun rolo-highlight-matches (regexp start end)
  899.   "Highlight matches for REGEXP in region from START to END."
  900.   (if (fboundp 'hproperty:but-add)
  901.       (let ((hproperty:but-emphasize-p))
  902.     (save-excursion
  903.       (goto-char start)
  904.       (while (re-search-forward regexp nil t)
  905.         (hproperty:but-add (match-beginning 0) (match-end 0)
  906.                    (or rolo-highlight-face
  907.                    hproperty:highlight-face)))))))
  908.  
  909. (defun rolo-kill-buffer (&optional rolo-buf)
  910.   "Kills optional ROLO-BUF if unchanged and 'rolo-kill-buffers-after-use' is t.
  911. Default is current buffer."
  912.   (or rolo-buf (setq rolo-buf (current-buffer)))
  913.   (and rolo-kill-buffers-after-use (not (buffer-modified-p rolo-buf))
  914.        (kill-buffer rolo-buf)))
  915.  
  916. (defun rolo-name-and-email ()
  917.   "If point is in a mail message, returns list of (name email-addr) of sender.
  918. Name is returned as 'last, first-and-middle'."
  919.   (save-excursion
  920.     (save-restriction
  921.       (goto-char (point-min))
  922.       (if (search-forward "\n\n" nil t)
  923.       (narrow-to-region (point-min) (point)))
  924.       (let ((email) (name) (from))
  925.     (setq email (mail-fetch-field "reply-to")
  926.           from  (mail-fetch-field "from"))
  927.     (if from
  928.         (cond
  929.          ;; Match: email, email (name), email "name"
  930.          ((string-match
  931.            (concat "^\\([^\"<>() \t\n]+\\)"
  932.                "\\([ \t]*[(\"][ \t]*\\([^\"()]+\\)[ \t]+"
  933.                "\\([^\" \t()]+\\)[ \t]*[)\"]\\)?[ \t]*$")
  934.            from)
  935.           (setq name (rolo-format-name from 3 4))
  936.           (or email (setq email (substring from (match-beginning 1)
  937.                            (match-end 1)))))
  938.          ;; Match: <email>, name <email>, "name" <email>
  939.          ((string-match
  940.            (concat "^\\(\"?\\([^\"<>()\n]+\\)[ \t]+"
  941.                "\\([^\" \t()<>]+\\)\"?[ \t]+\\)?"
  942.                "<\\([^\"<>() \t\n]+\\)>[ \t]*$")
  943.            from)
  944.           (setq name (rolo-format-name from 2 3))
  945.           (or email (setq email (substring from (match-beginning 4)
  946.                            (match-end 4)))))))
  947.     (if (or name email)
  948.         (list name email))))))
  949.  
  950. (defun rolo-name-at ()
  951.   "If point is within an entry in 'rolo-display-buffer', returns entry, else nil."
  952.   (if (string-equal (buffer-name) rolo-display-buffer)
  953.       (save-excursion
  954.     (if (or (looking-at rolo-entry-regexp)
  955.         (progn (end-of-line)
  956.                (re-search-backward rolo-entry-regexp nil t)))
  957.         (progn (goto-char (match-end 0))
  958.            (skip-chars-forward " \t")
  959.            (if (or (looking-at "[^ \t\n\^M]+ ?, ?[^ \t\n\^M]+")
  960.                (looking-at "\\( ?[^ \t\n\^M]+\\)+"))
  961.                (buffer-substring (match-beginning 0)
  962.                      (match-end 0))))))))
  963.  
  964. (defun rolo-narrowed-p ()
  965.   (or (/= (point-min) 1) (/= (1+ (buffer-size)) (point-max))))
  966.  
  967. (defun rolo-save-buffer (&optional rolo-buf)
  968.   "Saves optional ROLO-BUF if changed and 'rolo-save-buffers-after-use' is t.
  969. Default is current buffer.  Used, for example, after a rolo entry is killed."
  970.   (or rolo-buf (setq rolo-buf (current-buffer)))
  971.   (and rolo-save-buffers-after-use (buffer-modified-p rolo-buf)
  972.        (set-buffer rolo-buf) (save-buffer)))
  973.  
  974. (defun rolo-shrink-window ()
  975.   (let* ((lines (count-lines (point-min) (point-max)))
  976.      (height (window-height))
  977.      (window-min-height 2)
  978.      (desired-shrinkage (1- (min (- height lines)))))
  979.     (and (>= lines 0)
  980.      (/= desired-shrinkage 0)
  981.      (> (frame-height) (1+ height))
  982.      (shrink-window 
  983.        (if (< desired-shrinkage 0)
  984.            (max desired-shrinkage (- height (/ (frame-height) 2)))
  985.   (min desired-shrinkage (- height window-min-height)))))))
  986.  
  987. (defun rolo-to (name &optional file-list)
  988.   "Moves point to entry for NAME within optional FILE-LIST.
  989. 'rolo-file-list' is used as default when FILE-LIST is nil.
  990. Leaves point immediately after match for NAME within entry.
  991. Switches internal current buffer but does not alter the frame.
  992. Returns point where matching entry begins or nil if not found."
  993.   (or file-list (setq file-list rolo-file-list))
  994.   (let ((found) file)
  995.     (while (and (not found) file-list)
  996.       (setq file (car file-list)
  997.         file-list (cdr file-list))
  998.       (cond ((and file (or (not (stringp file)) (string= file "")))
  999.          (error "(rolo-to): Invalid file: '%s'" file))
  1000.         ((and (file-exists-p file) (not (file-readable-p file)))
  1001.          (error "(rolo-to): File not readable: '%s'" file)))
  1002.       (set-buffer (or (get-file-buffer file) (find-file-noselect file)))
  1003.       (let ((case-fold-search t) (real-name name) (parent "") (level) end)
  1004.     (widen) (goto-char 1)
  1005.     (while (setq end (string-match "/" name))
  1006.       (setq level nil
  1007.         parent (substring name 0 end)
  1008.         name (substring name (min (1+ end) (length name))))
  1009.       (cond ((progn
  1010.            (while (and (not level) (search-forward parent nil t))
  1011.              (save-excursion
  1012.                (beginning-of-line)
  1013.                (if (looking-at
  1014.                 (concat "\\(" rolo-entry-regexp "\\)[ \t]*" 
  1015.                     (regexp-quote parent)))
  1016.                (setq level (buffer-substring (match-beginning 1)
  1017.                              (match-end 1))))))
  1018.            level))
  1019.         ((equal name real-name));; Try next file.
  1020.         (t;; Found parent but not child
  1021.          (setq buffer-read-only nil)
  1022.          (rolo-to-buffer (current-buffer))
  1023.          (error "(rolo-to): '%s' part of name not found in \"%s\"."
  1024.             parent file)))
  1025.       (if level
  1026.           (narrow-to-region (point)
  1027.                 (save-excursion
  1028.                   (rolo-to-entry-end t level) (point)))))
  1029.     (goto-char (point-min))
  1030.     (while (and (search-forward name nil t)
  1031.             (not (save-excursion
  1032.                (beginning-of-line)
  1033.                (setq found
  1034.                  (if (looking-at
  1035.                       (concat "\\(" rolo-entry-regexp
  1036.                           "\\)[ \t]*"
  1037.                           (regexp-quote name)))
  1038.                      (point))))))))
  1039.       (or found (rolo-kill-buffer))) ;; conditionally kill
  1040.     (widen)
  1041.     found))
  1042.  
  1043. (defun rolo-to-buffer (buffer &optional other-window-flag frame)
  1044.   "Pop to BUFFER."
  1045.   (cond (hyperb:lemacs-p
  1046.       (pop-to-buffer buffer other-window-flag
  1047.              ;; default is to use selected frame
  1048.              (or frame (selected-frame))))
  1049.     (t (pop-to-buffer buffer other-window-flag))))
  1050.  
  1051. (defun rolo-to-entry-end (&optional include-sub-entries curr-entry-level)
  1052. "Goes to end of whole entry if optional INCLUDE-SUB-ENTRIES is non-nil.
  1053. CURR-ENTRY-LEVEL is a string whose length is the same as the last found entry
  1054. header.  If INCLUDE-SUB-ENTRIES is nil, CURR-ENTRY-LEVEL is not needed.
  1055. Returns current point."
  1056.   (while (and (setq next-entry-exists
  1057.             (re-search-forward rolo-entry-regexp nil t))
  1058.           include-sub-entries
  1059.           (> (- (point) (save-excursion
  1060.                   (beginning-of-line)
  1061.                   (point)))
  1062.          (length curr-entry-level))))
  1063.   (if next-entry-exists
  1064.       (progn (beginning-of-line) (point))
  1065.     (goto-char (point-max))))
  1066.  
  1067. (defun wrolo-mode ()
  1068.   "Major mode for the rolodex match buffer.
  1069. Calls the functions given by `wrolo-mode-hook'.
  1070. \\{wrolo-mode-map}"
  1071.   (interactive)
  1072.   (setq major-mode 'wrolo-mode
  1073.     mode-name "Rolodex")
  1074.   (use-local-map wrolo-mode-map)
  1075.   ;;
  1076.   ;; Loads menus under non-tty InfoDock, XEmacs or Emacs19; does nothing
  1077.   ;; otherwise.
  1078.   (and (not (featurep 'wrolo-menu)) hyperb:window-system
  1079.        (or hyperb:lemacs-p hyperb:emacs19-p) (require 'wrolo-menu))
  1080.   ;;
  1081.   (if (not (fboundp 'outline-minor-mode))
  1082.       nil
  1083.     (outline-minor-mode 1))
  1084.   (run-hooks 'wrolo-mode-hook))
  1085.  
  1086. ;;; ************************************************************************
  1087. ;;; Private variables
  1088. ;;; ************************************************************************
  1089.  
  1090. (defvar rolo-display-buffer "*Rolodex*"
  1091.   "Buffer used to display set of last matching rolodex entries.")
  1092.  
  1093. (defvar rolo-entry-regexp "^\\*+"
  1094.   "Regular expression to match the beginning of a rolodex entry.
  1095. This pattern must match the beginning of the line.  Entries may be nested
  1096. through the use of increasingly longer beginning patterns.")
  1097.  
  1098. (defconst rolo-hdr-format
  1099.   (concat
  1100.    "======================================================================\n"
  1101.    "%s\n"
  1102.    "======================================================================\n")
  1103.   "Header to insert preceding a file's first rolodex entry match when
  1104. file has none of its own.  Used with one argument, the file name."
  1105. )
  1106.  
  1107. (defconst rolo-hdr-regexp "^==="
  1108.   "Regular expression to match the first and last lines of rolodex file headers.
  1109. This header is inserted into rolo-display-buffer before any entries from the
  1110. file are added.")
  1111.  
  1112. (defconst rolo-match-regexp nil
  1113.   "Last regular expression used to search the rolodex.
  1114. Nil before a search is done.
  1115. String search expressions are converted to regular expressions.")
  1116.  
  1117. (defvar *rolo-wconfig* nil
  1118.   "Saves frame's window configuration prior to a rolodex search.")
  1119.  
  1120. (defvar wrolo-mode-map nil
  1121.   "Keymap for the rolodex match buffer.")
  1122.  
  1123. (if wrolo-mode-map
  1124.     nil
  1125.   (setq wrolo-mode-map (make-keymap))
  1126.   (if (fboundp 'set-keymap-name)
  1127.       (set-keymap-name wrolo-mode-map 'wrolo-mode-map))
  1128.   (suppress-keymap wrolo-mode-map)
  1129.   (define-key wrolo-mode-map "<"        'beginning-of-buffer)
  1130.   (define-key wrolo-mode-map ">"        'end-of-buffer)
  1131.   (define-key wrolo-mode-map "."        'beginning-of-buffer)
  1132.   (define-key wrolo-mode-map ","        'end-of-buffer)
  1133.   (define-key wrolo-mode-map "?"        'describe-mode)
  1134.   (define-key wrolo-mode-map "\177"     'scroll-down)
  1135.   (define-key wrolo-mode-map " "        'scroll-up)
  1136.   (define-key wrolo-mode-map "a"        'show-all)
  1137.   (define-key wrolo-mode-map "b"        'outline-backward-same-level)
  1138.   (define-key wrolo-mode-map "f"        'outline-forward-same-level)
  1139.   (define-key wrolo-mode-map "h"        'hide-subtree)
  1140.   (define-key wrolo-mode-map "n"        'outline-next-visible-heading)
  1141.   (define-key wrolo-mode-map "p"        'outline-previous-visible-heading)
  1142.   (define-key wrolo-mode-map "q"        'rolo-quit)
  1143.   (define-key wrolo-mode-map "r"        'rolo-previous-match)
  1144.   (define-key wrolo-mode-map "s"        'show-subtree)
  1145.   (define-key wrolo-mode-map "t"        'hide-body)
  1146.   (define-key wrolo-mode-map "\C-i"     'rolo-next-match)      ;; {TAB}
  1147.   (define-key wrolo-mode-map "\M-\C-i"  'rolo-previous-match)  ;; {M-TAB}
  1148.   (define-key wrolo-mode-map "u"        'outline-up-heading)
  1149.   )
  1150.  
  1151. (provide 'wrolo)
  1152.